package org.byacko.simplemock;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * メソッドフックを管理するクラス
 * @author koumiya
 *
 */
public class MethodHooks {
	
	protected MethodHooks() {}

	private Map<String, MethodHook> noArgHooks = new HashMap<String, MethodHook>();  
	private Map<List<Object>, MethodHook> withArgHooks = new HashMap<List<Object>, MethodHook>();
	
	/**
	 * メソッドフックを検索する。
	 * メソッド名と引数の型が一致するものが登録されている場合それを返す。
	 * findNoArgにtrueが指定された場合メソッド名は一致するが、引数の型が一致するものがない場合引数なしのメソッドフックを返す。
	 * @param method
	 * @param findNoArg
	 * @return メソッド名と引数の型が一致するメソッドフック
	 */
	public MethodHook lookup(Method method, boolean findNoArg) {
		if(isNoArg(method)) {
			return noArgHooks.get(method.getName());
		} else {
			final List<Object> key = createKey(method.getName(), method.getParameterTypes());
			final MethodHook withArgHook = withArgHooks.get(key);
			return withArgHook == null && findNoArg ? noArgHooks.get(method.getName()) : withArgHook;
		}
	}

	/**
	 * メソッドフックを登録する
	 * @param methodName
	 * @param argTypes
	 * @param returnValue
	 */
	public void bind(MethodHook mh) {
		if(mh.isNoArgs()) {
			putNoArgHook(mh);
		} else {
			putWithArgHook(mh);
		}
	}

	private void putWithArgHook(MethodHook mf) {
		final List<Object> key = createKey(mf.getMethodName(), mf.getArgTypes());
		withArgHooks.put(key, mf);
	}

	private List<Object> createKey(String methodName, Class<?>[] argTypes) {
		final List<Object> key = new ArrayList<Object>();
		key.add(methodName);
		key.add(Arrays.asList(argTypes));
		return key;
	}

	private void putNoArgHook(MethodHook mh) {
		noArgHooks.put(mh.getMethodName(), mh);
	}
	
	private boolean isNoArg(Method method) {
		return method.getParameterTypes().length == 0;
	}

}
